#pragma once

#include <cstdint>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <cstring>
#include <functional>
#include <string>

#include "../crow/settings.h"

namespace crow {
namespace black_magic {

#ifndef CROW_MSVC_WORKAROUND
struct OutOfRange {
    OutOfRange(unsigned /*pos*/, unsigned /*length*/) {}
};

constexpr unsigned requires_in_range(unsigned i, unsigned len) {
    return i >= len ? throw OutOfRange(i, len) : i;
}

class const_str {
    const char * const begin_;
    unsigned size_;

public:
    template<unsigned N>
    constexpr const_str(const char (&arr)[N]) :
    begin_(arr), size_(N - 1) {
        static_assert( N >= 1, "not a string literal");
    }

    constexpr char operator[](unsigned i) const {
        return requires_in_range(i, size_), begin_[i];
    }

    constexpr operator const char *() const {
        return begin_;
    }

    constexpr const char* begin() const {
        return begin_;
    }

    constexpr const char* end() const {
        return begin_ + size_;
    }

    constexpr unsigned size() const {
        return size_;
    }
};

constexpr unsigned find_closing_tag(const_str s, unsigned p) {
    return s[p] == '>' ? p : find_closing_tag(s, p + 1);
}

constexpr bool is_valid(const_str s, unsigned i = 0, int f = 0) {
    return i == s.size() ? f == 0 : f < 0 || f >= 2 ? false : s[i] == '<' ? is_valid(s, i + 1, f + 1) :
           s[i] == '>' ? is_valid(s, i + 1, f - 1) : is_valid(s, i + 1, f);
}

constexpr bool is_equ_p(const char* a, const char* b, unsigned n) {
    return *a == 0 && *b == 0 && n == 0 ? true : (*a == 0 || *b == 0) ? false : n == 0 ? true :
           *a != *b ? false : is_equ_p(a + 1, b + 1, n - 1);
}

constexpr bool is_equ_n(const_str a, unsigned ai, const_str b, unsigned bi, unsigned n) {
    return ai + n > a.size() || bi + n > b.size() ? false : n == 0 ? true : a[ai] != b[bi] ? false : is_equ_n(a, ai + 1, b, bi + 1, n - 1);
}

constexpr bool is_int(const_str s, unsigned i) {
    return is_equ_n(s, i, "<int>", 0, 5);
}

constexpr bool is_uint(const_str s, unsigned i) {
    return is_equ_n(s, i, "<uint>", 0, 6);
}

constexpr bool is_float(const_str s, unsigned i) {
    return is_equ_n(s, i, "<float>", 0, 7) || is_equ_n(s, i, "<double>", 0, 8);
}

constexpr bool is_str(const_str s, unsigned i) {
    return is_equ_n(s, i, "<str>", 0, 5) || is_equ_n(s, i, "<string>", 0, 8);
}

constexpr bool is_path(const_str s, unsigned i) {
    return is_equ_n(s, i, "<path>", 0, 6);
}
#endif
template<typename T>
struct parameter_tag {
    static const int value = 0;
};
#define CROW_INTERNAL_PARAMETER_TAG(t, i) \
template <> \
struct parameter_tag<t> \
{ \
    static const int value = i; \
}
CROW_INTERNAL_PARAMETER_TAG(int, 1);CROW_INTERNAL_PARAMETER_TAG(char, 1);
CROW_INTERNAL_PARAMETER_TAG(short, 1);CROW_INTERNAL_PARAMETER_TAG(long, 1);
CROW_INTERNAL_PARAMETER_TAG(long long, 1);CROW_INTERNAL_PARAMETER_TAG(unsigned int, 2);
CROW_INTERNAL_PARAMETER_TAG(unsigned char, 2);CROW_INTERNAL_PARAMETER_TAG(unsigned short, 2);
CROW_INTERNAL_PARAMETER_TAG(unsigned long, 2);CROW_INTERNAL_PARAMETER_TAG(unsigned long long, 2);
CROW_INTERNAL_PARAMETER_TAG(double, 3);CROW_INTERNAL_PARAMETER_TAG(std::string, 4);
#undef CROW_INTERNAL_PARAMETER_TAG
template<typename ... Args>
struct compute_parameter_tag_from_args_list;

template<>
struct compute_parameter_tag_from_args_list<> {
    static const int value = 0;
};

template<typename Arg, typename ... Args>
struct compute_parameter_tag_from_args_list<Arg, Args...> {
    static const int sub_value = compute_parameter_tag_from_args_list<Args...>::value;
    static const int value =
    parameter_tag<typename std::decay<Arg>::type>::value ? sub_value * 6 + parameter_tag<typename std::decay<Arg>::type>::value : sub_value;
};

static inline bool is_parameter_tag_compatible(uint64_t a, uint64_t b) {
    if (a == 0) return b == 0;
    if (b == 0) return a == 0;
    int sa = a % 6;
    int sb = a % 6;
    if (sa == 5) sa = 4;
    if (sb == 5) sb = 4;
    if (sa != sb) return false;
    return is_parameter_tag_compatible(a / 6, b / 6);
}

static inline unsigned find_closing_tag_runtime(const char* s, unsigned p) {
    return s[p] == 0 ? throw std::runtime_error("unmatched tag <") : s[p] == '>' ? p : find_closing_tag_runtime(s, p + 1);
}

static inline uint64_t get_parameter_tag_runtime(const char* s, unsigned p = 0) {
    return
    s[p] == 0 ? 0 :
    s[p] == '<' ?
    (
    std::strncmp(s + p, "<int>", 5) == 0 ? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 1 :
    std::strncmp(s + p, "<uint>", 6) == 0 ? get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 2 :
    (std::strncmp(s + p, "<float>", 7) == 0 || std::strncmp(s + p, "<double>", 8) == 0) ?
    get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 3 :
    (std::strncmp(s + p, "<str>", 5) == 0 || std::strncmp(s + p, "<string>", 8) == 0) ?
    get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 4 :
    std::strncmp(s + p, "<path>", 6) == 0 ?
    get_parameter_tag_runtime(s, find_closing_tag_runtime(s, p)) * 6 + 5 : throw std::runtime_error("invalid parameter type")) :
    get_parameter_tag_runtime(s, p + 1);
}
#ifndef CROW_MSVC_WORKAROUND
constexpr uint64_t get_parameter_tag(const_str s, unsigned p = 0) {
    return
    p == s.size() ? 0 :
    s[p] == '<' ?
    (is_int(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 1 :
     is_uint(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 2 :
     is_float(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 3 :
     is_str(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 4 :
     is_path(s, p) ? get_parameter_tag(s, find_closing_tag(s, p)) * 6 + 5 : throw std::runtime_error("invalid parameter type")) :
    get_parameter_tag(s, p + 1);
}
#endif

template<typename ... T>
struct S {
    template<typename U>
    using push = S<U, T...>;
    template<typename U>
    using push_back = S<T..., U>;
    template<template<typename ... Args> class U>
    using rebind = U<T...>;
};

template<typename F, typename Set>
struct CallHelper;

template<typename F, typename ...Args>
struct CallHelper<F, S<Args...>> {
    template<typename F1, typename ...Args1, typename = decltype(std::declval<F1>()(std::declval<Args1>()...))>
    static char __test(int);

    template<typename ...>
    static int __test(...);

    static constexpr bool value = sizeof(__test<F, Args...>(0)) == sizeof(char);
};

template<int N>
struct single_tag_to_type {
};

template<>
struct single_tag_to_type<1> {
    using type = int64_t;
};

template<>
struct single_tag_to_type<2> {
    using type = uint64_t;
};

template<>
struct single_tag_to_type<3> {
    using type = double;
};

template<>
struct single_tag_to_type<4> {
    using type = std::string;
};

template<>
struct single_tag_to_type<5> {
    using type = std::string;
};

template<uint64_t Tag>
struct arguments {
    using subarguments = typename arguments<Tag/6>::type;
    using type =
    typename subarguments::template push<typename single_tag_to_type<Tag%6>::type>;
};

template<>
struct arguments<0> {
    using type = S<>;
};

template<typename ... T>
struct last_element_type {
    using type = typename std::tuple_element<sizeof...(T)-1, std::tuple<T...>>::type;
};

template<>
struct last_element_type<> {
};

// from http://stackoverflow.com/questions/13072359/c11-compile-time-array-with-logarithmic-evaluation-depth
template<class T> using Invoke = typename T::type;

template<unsigned...> struct seq {using type = seq;};

template<class S1, class S2> struct concat;

template<unsigned ... I1, unsigned ... I2>
struct concat<seq<I1...>, seq<I2...>> : seq<I1..., (sizeof...(I1)+I2)...> {
};

template<class S1, class S2>
using Concat = Invoke<concat<S1, S2>>;

template<unsigned N> struct gen_seq;
template<unsigned N> using GenSeq = Invoke<gen_seq<N>>;

template<unsigned N>
struct gen_seq: Concat<GenSeq<N / 2>, GenSeq<N - N / 2>> {
};

template<> struct gen_seq<0> : seq<> {
};

template<> struct gen_seq<1> : seq<0> {
};

template<typename Seq, typename Tuple>
struct pop_back_helper;

template<unsigned ... N, typename Tuple>
struct pop_back_helper<seq<N...>, Tuple> {
    template<template<typename ... Args> class U>
    using rebind = U<typename std::tuple_element<N, Tuple>::type...>;
};

template<typename ... T>
struct pop_back //: public pop_back_helper<typename gen_seq<sizeof...(T)-1>::type, std::tuple<T...>>
{
    template<template<typename ... Args> class U>
    using rebind = typename pop_back_helper<typename gen_seq<sizeof...(T)-1>::type, std::tuple<T...>>::template rebind<U>;
};

template<>
struct pop_back<> {
    template<template<typename ... Args> class U>
    using rebind = U<>;
};

// from http://stackoverflow.com/questions/2118541/check-if-c0x-parameter-pack-contains-a-type
template<typename Tp, typename ... List>
struct contains: std::true_type {
};

template<typename Tp, typename Head, typename ... Rest>
struct contains<Tp, Head, Rest...> : std::conditional<std::is_same<Tp, Head>::value, std::true_type, contains<Tp, Rest...> >::type {
};

template<typename Tp>
struct contains<Tp> : std::false_type {
};

template<typename T>
struct empty_context {
};

template<typename T>
struct promote {
    using type = T;
};

#define CROW_INTERNAL_PROMOTE_TYPE(t1, t2) \
    template<> \
    struct promote<t1> \
    {  \
        using type = t2; \
    }

CROW_INTERNAL_PROMOTE_TYPE(char, int64_t);CROW_INTERNAL_PROMOTE_TYPE(short, int64_t);
CROW_INTERNAL_PROMOTE_TYPE(int, int64_t);CROW_INTERNAL_PROMOTE_TYPE(long, int64_t);
CROW_INTERNAL_PROMOTE_TYPE(long long, int64_t);CROW_INTERNAL_PROMOTE_TYPE(unsigned char, uint64_t);
CROW_INTERNAL_PROMOTE_TYPE(unsigned short, uint64_t);CROW_INTERNAL_PROMOTE_TYPE(unsigned int, uint64_t);
CROW_INTERNAL_PROMOTE_TYPE(unsigned long, uint64_t);CROW_INTERNAL_PROMOTE_TYPE(unsigned long long, uint64_t);
CROW_INTERNAL_PROMOTE_TYPE(float, double);
#undef CROW_INTERNAL_PROMOTE_TYPE

template<typename T>
using promote_t = typename promote<T>::type;

}
 // namespace black_magic

namespace detail {

template<class T, std::size_t N, class ... Args>
struct get_index_of_element_from_tuple_by_type_impl {
    static constexpr auto value = N;
};

template<class T, std::size_t N, class ... Args>
struct get_index_of_element_from_tuple_by_type_impl<T, N, T, Args...> {
    static constexpr auto value = N;
};

template<class T, std::size_t N, class U, class ... Args>
struct get_index_of_element_from_tuple_by_type_impl<T, N, U, Args...> {
    static constexpr auto value = get_index_of_element_from_tuple_by_type_impl<T, N + 1, Args...>::value;
};

} // namespace detail

namespace utility {
template<class T, class ... Args>
T& get_element_by_type(std::tuple<Args...>& t) {
    return std::get < detail::get_index_of_element_from_tuple_by_type_impl<T, 0, Args...>::value > (t);
}

template<typename T>
struct function_traits;

#ifndef CROW_MSVC_WORKAROUND
template<typename T>
struct function_traits: public function_traits<decltype(&T::operator())> {
    using parent_t = function_traits<decltype(&T::operator())>;
    static const size_t arity = parent_t::arity;
    using result_type = typename parent_t::result_type;
    template<size_t i>
    using arg = typename parent_t::template arg<i>;
};
#endif

template<typename ClassType, typename R, typename ...Args>
struct function_traits<R (ClassType::*)(Args...) const> {
static const size_t arity = sizeof...(Args);

typedef R result_type;

template <size_t i>
using arg = typename std::tuple_element<i, std::tuple<Args...>>::type;
};

template<typename ClassType, typename R, typename ...Args>
struct function_traits<R (ClassType::*)(Args...)> {
static const size_t arity = sizeof...(Args);

typedef R result_type;

template <size_t i>
using arg = typename std::tuple_element<i, std::tuple<Args...>>::type;
};

template<typename R, typename ...Args>
struct function_traits<std::function<R(Args...)>> {
static const size_t arity = sizeof...(Args);

typedef R result_type;

template <size_t i>
using arg = typename std::tuple_element<i, std::tuple<Args...>>::type;
};

inline static std::string base64encode(const char* data, size_t size, const char* key =
                                       "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") {
    std::string ret;
    ret.resize((size + 2) / 3 * 4);
    auto it = ret.begin();
    while (size >= 3) {
        *it++ = key[(((unsigned char)*data) & 0xFC) >> 2];
        unsigned char h = (((unsigned char)*data++) & 0x03) << 4;
        *it++ = key[h | ((((unsigned char)*data) & 0xF0) >> 4)];
        h = (((unsigned char)*data++) & 0x0F) << 2;
        *it++ = key[h | ((((unsigned char)*data) & 0xC0) >> 6)];
        *it++ = key[((unsigned char)*data++) & 0x3F];

        size -= 3;
    }
    if (size == 1) {
        *it++ = key[(((unsigned char)*data) & 0xFC) >> 2];
        unsigned char h = (((unsigned char)*data++) & 0x03) << 4;
        *it++ = key[h];
        *it++ = '=';
        *it++ = '=';
    }
    else if (size == 2) {
        *it++ = key[(((unsigned char)*data) & 0xFC) >> 2];
        unsigned char h = (((unsigned char)*data++) & 0x03) << 4;
        *it++ = key[h | ((((unsigned char)*data) & 0xF0) >> 4)];
        h = (((unsigned char)*data++) & 0x0F) << 2;
        *it++ = key[h];
        *it++ = '=';
    }
    return ret;
}

inline static std::string base64encode_urlsafe(const char* data, size_t size) {
    return base64encode(data, size, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_");
}

} // namespace utility

}

